#!/usr/bin/python

# By Jackson Yee
# Copyright 2007
#
# This program is released under the terms of the GPL version 2
# available at http://www.gnu.org/copyleft/gpl.html
#
# Feel free to modify or distribute, but please send me some patches
# if you add any features.

import wx
import wx.html
import wx.lib.newevent
import wxptag
import time
import ftputil

AppName = "Possum's Website Synchronizer"
AppVersion = '2007.05.23'
conf = possum.Config(AppName)

SyncUpdateEvent, EVT_SYNC_UPDATE = wx.lib.newevent.NewEvent()

# *********************************************************************
class HtmlPanelBase(wx.Panel):
	
	# -------------------------------------------------------------------
	def __init__(self, parent):
		wx.Panel.__init__(self, parent, wx.ID_ANY)
		
		self.HtmlWnd = wx.html.HtmlWindow(self, 
			wx.ID_ANY, 
			style = wx.TAB_TRAVERSAL)
		self.HtmlWnd.SetBorders(0)
		
		self.MainSizer = wx.BoxSizer( wx.VERTICAL )
		self.MainSizer.Add(self.HtmlWnd, 1, wx.EXPAND)
		
		self.SetSizer( self.MainSizer) 
		
		self.Bind(wx.html.EVT_HTML_LINK_CLICKED, self.OnLink)
	
	# -------------------------------------------------------------------
	def OnLink(self, e):
		pass

# *********************************************************************
class LogDialog(wx.Dialog):
	
	# -------------------------------------------------------------------
	def __init__(self, parent, sync):
		wx.Dialog.__init__(self, 
			parent, 
			wx.ID_ANY, 
			'Log Viewer',
			style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
		
		self.LogCtrl = wx.TextCtrl(self, 
				wx.ID_ANY, 
				style = wx.TE_MULTILINE | wx.TE_READONLY)
		self.SaveButton = wx.Button(self, wx.ID_SAVE, 'Save Log')
		self.CloseButton = wx.Button(self, wx.ID_CANCEL, 'Close')
		
		self.MainSizer = wx.BoxSizer(wx.VERTICAL)
		self.ButtonSizer = wx.BoxSizer(wx.HORIZONTAL)
		
		self.ButtonSizer.Add( (0, 0), 1, wx.EXPAND | wx.ALL, 4)
		self.ButtonSizer.Add( self.SaveButton, 1, wx.EXPAND | wx.ALL, 4)
		self.ButtonSizer.Add( self.CloseButton, 1, wx.EXPAND | wx.ALL, 4)
		
		self.MainSizer.Add( self.LogCtrl, 6, wx.EXPAND )
		self.MainSizer.Add( self.ButtonSizer, 1, wx.EXPAND )
		self.SetSizer(self.MainSizer)
		
		self.Bind(wx.EVT_BUTTON, self.OnSave, id = wx.ID_SAVE)
		self.Bind(wx.EVT_BUTTON, self.OnCancel, id = wx.ID_CANCEL)
		
		for l in sync.Log:
			s = l[0]
			
			if l[1]:
				s += '\t' + l[1]
				
			if l[2]:
				s += '\t' + l[2]
			
			self.LogCtrl.AppendText(s + '\n')
		
	# -------------------------------------------------------------------
	def OnSave(self, e):
		f = wx.FileSelector('Please choose a file to save the log to', 
					default_extension = '.txt',
					parent = self)
		
		if f:
			self.LogCtrl.SaveFile(f)
		
	# -------------------------------------------------------------------
	def OnCancel(self, e):
		self.EndModal( wx.ID_CANCEL )

# *********************************************************************
class FTPDirDialog(wx.Dialog):
	
	# -------------------------------------------------------------------
	def __init__(self, parent, server, username, password, dirstoignore):
		wx.Dialog.__init__(self, 
			parent, 
			wx.ID_ANY, 
			'Choose a FTP directory',
			style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
		
		self.Server = server
		self.UserName = username
		self.Password = password
		
		self.Tree = wx.TreeCtrl(self, 
			wx.ID_ANY, 
			style = wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT 
				| wx.TR_ROW_LINES)
		
		self.Status = wx.StaticText(self, 
			wx.ID_ANY, 
			'Connecting... please wait a minute')
		
		self.ChooseButton = wx.Button(self, wx.ID_OK, 'Choose')
		self.CancelButton = wx.Button(self, wx.ID_CANCEL, 'Cancel')
		
		self.MainSizer = wx.BoxSizer(wx.VERTICAL)
		self.ButtonSizer = wx.BoxSizer(wx.HORIZONTAL)
		
		self.ButtonSizer.Add( (0,0), 1, wx.EXPAND)
		self.ButtonSizer.Add( self.ChooseButton, 1, wx.EXPAND | wx.ALL, 4)
		self.ButtonSizer.Add( self.CancelButton, 1, wx.EXPAND | wx.ALL, 4)
		
		self.MainSizer.Add( self.Status, 2, wx.EXPAND | wx.ALL, 4)
		self.MainSizer.Add( self.Tree, 5, wx.EXPAND)
		self.MainSizer.Add( self.ButtonSizer, 1, wx.EXPAND )
		
		self.SetSizer(self.MainSizer)
		
		self.Path = '/'		
		self.Tree.AddRoot('/')		
		self.Errors = []
		self.DirsToIgnore = []
		
		for d in dirstoignore:
			self.DirsToIgnore.append( re.compile(d) )
		
		self.Timer = wx.Timer(self)
		
		self.Bind(wx.EVT_TIMER, self.OnTimer)
		self.Bind(wx.EVT_BUTTON, self.OnOK, id = wx.ID_OK)
		self.Bind(wx.EVT_BUTTON, self.OnCancel, id = wx.ID_CANCEL)
		
		self.Timer.Start(1000, True)		
		
	# -------------------------------------------------------------------
	def OnTimer(self, e):
		try:
			self.FTP = ftputil.FTPHost(self.Server, 
														self.UserName, 
														self.Password)
		except Exception, e:
			wx.MessageBox(str(e), 'Unable to connect', wx.ICON_HAND, self)
			self.EndModal( wx.ID_CANCEL )
			return
		
		self.Status.SetLabel('Scanning for directories...')
		self.Status.Refresh()
		self.Status.Update()
		
		self.ScanDirs( self.Tree.GetRootItem(), '/')
		
		self.FTP.close()
		
		if self.Errors and not self.Cancel:
			wx.MessageBox(str( self.Errors[0] ), 
				'Unable to scan server', 
				wx.ICON_HAND, 
				self)
			self.Status.SetLabel("""Finished scanning with %s errors.
Not all directories may be listed.""" % len(self.Errors) )
			return
			
		self.Status.SetLabel('Finished scanning. Ready for use.')
		
	# -------------------------------------------------------------------
	def ScanDirs(self, ti, path):
		self.Status.SetLabel('Scanning directory %s' % path)
		self.Status.Refresh()
		self.Status.Update()
		
		try:
			ls = self.FTP.listdir(path)
			
			ls.sort()
			
			for f in ls:
				match = False
				
				for d in self.DirsToIgnore:
					if d.match(f):
						match = True
						break
				
				if match:
					continue
				
				if path == '/':
					fullpath = path + f
				else:
					fullpath = path + '/' + f			
				
				wx.SafeYield(self)			
				
				if self.FTP.path.isdir(fullpath):
					i = self.Tree.AppendItem(ti, f)
					self.ScanDirs(i, fullpath)
		except Exception, e:
			self.Errors.append(e)
				
	# -------------------------------------------------------------------
	def OnOK(self, e):
		self.FTP.close()
		id = self.Tree.GetSelection()
		
		if id.IsOk():
			t = self.Tree.GetItemText(id)
			
			while id is not self.Tree.GetRootItem():
				id = self.Tree.GetItemParent(id)
				
				if id.IsOk():
					t = self.Tree.GetItemText(id) + '/' + t
				else:
					break
			
			self.Path = t.replace('//', '/')
		
		self.Cancel = True
		self.EndModal( wx.ID_OK )
		
	# -------------------------------------------------------------------
	def OnCancel(self, e):
		self.FTP.close()
		self.Cancel = True
		self.EndModal( wx.ID_CANCEL )

# *********************************************************************
class SyncThread(threading.Thread):

	# -------------------------------------------------------------------
	def __init__(self, site, wnd, autosync = False):
		threading.Thread.__init__(self)
		if site['type'] == 'local':
			self.Sync = FileSync(site['localdir'], 
											site['localdir2'],
											site['dirstoignore'],
											wnd)
		elif site['type'] == 'ftp':
			self.Sync = FTPSync(site['localdir'],
											site['remotedir'],
											site['dirstoignore'],
											wnd,
											site['server'], 
											site['username'],
											site['password'],
											)
			self.Sync.SetDownload(False)
		
		self.SyncEvent = threading.Event()
		self.AutoSync = autosync
		
	# -------------------------------------------------------------------
	def Cancel(self):
		self.Sync.Cancel = True
		self.SyncEvent.set()
		
	# -------------------------------------------------------------------
	def DoSync(self):
		self.SyncEvent.set()
		
	# -------------------------------------------------------------------
	def run(self):
		self.Sync.Scan()
		self.Sync.CalculateUpdates()
		
		if not self.AutoSync or len(self.Sync.ConflictFiles):
			self.SyncEvent.wait()
		
		self.Sync.Run()

# *********************************************************************
class SyncListCtrl(wx.ListCtrl):
	
	# -------------------------------------------------------------------
	def __init__(self, *args, **kwargs):
		wx.ListCtrl.__init__(self, *args, **kwargs)
		
		self.InsertColumn(0, 'Source File')
		self.InsertColumn(1, 'Action')
		self.InsertColumn(2, 'Destination File')
		
		self.SetColumnWidth(0, 300)
		self.SetColumnWidth(1, 60)
		self.SetColumnWidth(2, 300)

	# -------------------------------------------------------------------
	def SetSync(self, sync):
		self.Sync = sync
		self.NumSource = len(self.Sync.SourceFilesToCopy)
		self.NumDest = len(self.Sync.DestFilesToCopy)
		self.NumConflict = len(self.Sync.ConflictFiles) 
		self.NumDelete = len(self.Sync.DestFilesToDelete) 
		self.SetItemCount( self.NumSource 
			+ self.NumDest 
			+ self.NumConflict 
			+ self.NumDelete
		)
		
		print self.NumSource, self.NumDest, self.NumConflict, self.NumDelete
			
	# -------------------------------------------------------------------
	def OnGetItemText(self, i, c):
		if i >= self.NumSource:
			if i - self.NumSource >= self.NumDest:
				if i - self.NumSource - self.NumDest >= self.NumConflict:
					v = self.Sync.DestFilesToDelete[i - self.NumSource - self.NumDest - self.NumConflicts]
					
					if c == 0:
						return ''
					elif c == 1:
						return 'delete'
					elif c == 2:
						return v
						
				v = self.Sync.ConflictFiles[ i - self.NumSource - self.NumDest ]
				
				if c == 0:
					return v[0][0]
				elif c == 1:
					return '???'
				elif c == 2:
					return v[1][0]
			else:
				v = self.Sync.DestFilesToCopy[ i - self.NumSource ]
				
				if c == 0:
					return v[0][0]
				elif c == 1:
					return '<<<'
				elif c == 2:
					return os.path.basename( v[1][0] )
		else:
			v = self.Sync.SourceFilesToCopy[i]
			
			if c == 0:
				return os.path.basename( v[0][0] )
			elif c == 1:
				return '>>>'
			elif c == 2:
				return v[1][0]
		
		return '[Unknown]'

# *********************************************************************
class SyncPanel(HtmlPanelBase):
	(	ID_VIEW,
	) = range(0x1000, 0x1001)	
	
	# -------------------------------------------------------------------
	def __init__(self, parent, sitename, site, autosync = False):
		HtmlPanelBase.__init__(self, parent)
		
		self.ParentWnd = parent
		self.SiteName = sitename
		self.Site = site
		self.AutoSync = autosync
		
		if not autosync:
			buttons = """<wxp module="wx" class="Button" width="80" name="Sync" id="%s" label="Sync"></wxp>""" % wx.ID_OK
		else:
			buttons = ''
		
		s = """<html><body>
		<table width="100%%">
		<tr><td><img src="images/logo.png" /></td>
			<td align="right"><a href="Help">Help</a></td>
		</tr>
		</table>
		<table width="100%%" bgcolor="#aaddff" cellspacing="0">
		<tr><td colspan="2"><h3>%s</h3></td></tr>
		<tr><td>Status:</td><td><wxp module="wx" class="StaticText" width="100%%" name="Status" label="Initializing"></wxp></td></tr>		
		<tr><td>Progress:</td><td><wxp module="wx" class="Gauge" width="100%%" name="Gauge"></wxp></td></tr>		
		<tr><td></td><td><wxp module="wx" class="StaticText" width="100%%" name="BytesCopied" label=""></wxp></td></tr>		
		<tr><td>Source:</td><td><wxp module="wx" class="StaticText" width="100%%" name="Source" label=""></wxp></td></tr>		
		<tr><td>Destination:</td><td><wxp module="wx" class="StaticText" width="100%%" name="Dest" label=""></wxp></td></tr>		
		<tr bgcolor="#cccccc"><td></td>
		<td>
			%s
			<wxp module="wx" class="Button" width="80" name="ViewLog" id="%s" label="View Log"></wxp>
			<wxp module="wx" class="Button" width="80" name="Cancel" id="%s" label="Cancel"></wxp>
		</td></tr>
		<tr><td colspan="2">
			<wxp module="__main__" class="SyncListCtrl" name="SyncList" width="100%%" height="240" style="wx.LC_REPORT | wx.LC_VIRTUAL"></wxp>
		</td></tr>
		</table>""" % (sitename, buttons, self.ID_VIEW, wx.ID_CANCEL)
		self.HtmlWnd.SetPage(s)
		
		self.HtmlWnd.Gauge.SetRange( 400 ) 
		
		self.Timer = wx.Timer(self)
		self.Thread = None
		
		self.Bind(wx.EVT_TIMER, self.OnTimer)
		self.Bind(EVT_SYNC_UPDATE, self.OnSyncUpdate)
		self.Bind(wx.EVT_BUTTON, self.OnCancel, id = wx.ID_CANCEL)
		self.Bind(wx.EVT_BUTTON, self.OnViewLog, id = self.ID_VIEW)
		self.Bind(wx.EVT_CLOSE, self.OnClose)
		
		if not autosync:
			self.Bind(wx.EVT_BUTTON, self.OnSync, id = wx.ID_OK)
			self.HtmlWnd.Sync.Enable(False)
		
		self.Timer.Start(1000, True)
		
	# -------------------------------------------------------------------
	def OnTimer(self, e):
		self.Thread = SyncThread(self.Site, self, self.AutoSync)
		self.Thread.start()
		
	# -------------------------------------------------------------------
	def OnSyncUpdate(self, e):
		bytestocopy = self.Thread.Sync.SourceBytesToCopy + self.Thread.Sync.DestBytesToCopy
		bytescopied = self.Thread.Sync.SourceBytesCopied + self.Thread.Sync.DestBytesCopied
		
		# Skip calculating updates to save time
		if self.Thread.Sync.Status == FileSync.CALCULATING:
			return
		
		if self.Thread.Sync.Status == FileSync.FINISHED_CALCULATING:
			self.HtmlWnd.Source.SetLabel( self.Thread.Sync.SourceBaseDir )
			self.HtmlWnd.Dest.SetLabel( self.Thread.Sync.DestBaseDir )
			self.HtmlWnd.SyncList.SetSync( self.Thread.Sync )
			if bytestocopy:
				self.HtmlWnd.Sync.Enable(True)
		
		if self.Thread.Sync.Status == FileSync.SYNCING or self.Thread.Sync.Status == FileSync.FINISHED_SYNCING:
			self.HtmlWnd.Gauge.SetValue( float(bytescopied) / bytestocopy * 400 )
			self.HtmlWnd.BytesCopied.SetLabel( '%s of %s KiB copied' % 
				(bytescopied / 1024, bytestocopy / 1024) )
			
		if self.Thread.Sync.Status == FileSync.FINISHED_SYNCING:
			if not self.AutoSync:
				self.HtmlWnd.Sync.Enable(False)
			
			self.HtmlWnd.Cancel.SetLabel('Close')
			
		l = self.Thread.Sync.Log[-1]
		s = l[0]
		
		if l[1]:
			self.HtmlWnd.Source.SetLabel( l[1] )
			
		if l[2]:
			self.HtmlWnd.Dest.SetLabel( l[2] )
		
		self.HtmlWnd.Status.SetLabel( s )
		
	# -------------------------------------------------------------------
	def OnSync(self, e):
		self.Thread.DoSync()
		
	# -------------------------------------------------------------------
	def OnViewLog(self, e):
		d = LogDialog(self, self.Thread.Sync)
		d.ShowModal()
		
	# -------------------------------------------------------------------
	def OnCancel(self, e):
		self.Thread.Cancel()
			
		self.ParentWnd.DeletePage( self.ParentWnd.GetSelection() )
		
	# -------------------------------------------------------------------
	def OnClose(self, e):
		self.Thread.Cancel()

# *********************************************************************
class MainPanel(HtmlPanelBase):
	(	ID_LOCAL_DIR,
		ID_REMOTE_DIR,
		ID_LOCAL_DIR_2,
		ID_SAVE,
		ID_CANCEL,
		ID_ADD_IGNORE_DIR,
		ID_DELETE_IGNORE_DIR,
	) = range(0x1000, 0x1007)	
	
	# -------------------------------------------------------------------
	def __init__(self, parent):
		HtmlPanelBase.__init__(self, parent)
		
		self.ParentWnd = parent
		self.FTP = None
		
		if PossumConfig.Config.has_key('sites'):
			self.Sites = PossumConfig.Config['sites']
		else:
			self.Sites = {}
		
		self.LoadIndex()
		
	# -------------------------------------------------------------------
	def SaveSites(self):
		PossumConfig.Config['sites'] = self.Sites
			
	# -------------------------------------------------------------------
	def LoadIndex(self):		
		if self.Sites:
			sites = '<table>'
			
			ls = [k for k in self.Sites.iterkeys()]
			ls.sort()
			
			for k in ls:
				sites += """<tr><td><b>%s</b></td>
<td><a href="ScanSite/%s">Scan</a></td>
<td><a href="SyncSite/%s">Sync</a></td>
<td><a href="EditSite/%s">Edit</a></td>
<td><a href="DeleteSite/%s">Delete</a></td>
</tr>""" % (k, k, k, k, k)
			
			sites += '</table>'
		else:
			sites = '<p>[No sites have been created yet]</p>'
			
		s = """<html><body>
		<table width="100%%">
		<tr><td><img src="images/logo.png" /></td>
			<td align="right"><a href="Help">Help</a></td>
		</tr>
		</table>
		<table width="100%%">
		<tr bgcolor="#ccffcc"><td>
		<h3>Saved Sites</h3>
		%s
		</td></tr>
		<tr bgcolor="#ccccff"><td>
		<h3>New Site</h3>
		<ul>
			<li><a href="NewFTPSite">Create a new FTP site</a></li>
			<li><a href="NewLocalSite">Create a new local site</a></li>
		</ul>
		</td></tr>
		</table>
		</body></html>""" % sites
		self.HtmlWnd.SetPage(s)
		
	# -------------------------------------------------------------------
	def LoadFTPSite(self, sitename = ''):		
		if not sitename:
			site = {}
			site['server'] = ''
			site['username'] = ''
			site['password'] = ''
			site['localdir'] = ''
			site['remotedir'] = ''
			site['dirstoignore'] = ['Gallery.*', '_.*', 'cgi.*', '\..*']
		else:
			site = self.Sites[sitename]		
		
		s = """<html><body>
		<table width="100%%">
		<tr><td><img src="images/logo.png" /></td>
			<td align="right"><a href="Help">Help</a></td>
		</tr>
		</table>
		<table width="100%%" bgcolor="#ccffff" cellspacing="0">
		<tr><td colspan=3"><h3>Create New FTP Site</h3></td></tr>
		<tr><td>What would you like to call this site?</td>
				<td colspan="2"><wxp module="wx" class="TextCtrl" width="100%%" name="SiteName" value="%s"></wxp></td></tr>
		<tr><td>Your FTP Server:</td>
				<td colspan="2"><wxp module="wx" class="TextCtrl" width="100%%" name="FTPServer" value="%s">
				</wxp></td></tr>
		<tr><td>Your username:</td>
				<td colspan="2"><wxp module="wx" class="TextCtrl" width="100%%" name="UserName" value="%s"></wxp></td></tr>
		<tr><td>Your password:</td>
				<td colspan="2"><wxp module="wx" class="TextCtrl" width="100%%" name="Password" style="wx.TE_PASSWORD" value="%s"></wxp></td></tr>
		<tr bgcolor="#ccccff"><td colspan=3"><h3>Directories</h3></td></tr>
		<tr bgcolor="#ccccff"><td colspan="3">Please choose a directory on this computer and a directory on your server to synchronize:<br /><br />
As a friendly warning, using the browse button for remote sites can take a couple of minutes to scan for all of the remote directories.</td></tr>
		<tr bgcolor="#ccccff"><td>Local directory:</td>
				<td><wxp module="wx" class="TextCtrl" width="100%%" name="LocalDir" value="%s"></wxp></td>
				<td><wxp module="wx" class="Button" width="80" name="BrowseLocalDir" id="%s" label="Browse"></wxp></td></tr>
		<tr bgcolor="#ccccff"><td>Remote directory:</td>
				<td><wxp module="wx" class="TextCtrl" width="100%%" name="RemoteDir" value="%s"></wxp></td>
				<td><wxp module="wx" class="Button" width="80" name="BrowseRemoteDir" id="%s" label="Browse"></wxp></td></tr>
		<tr bgcolor="#ccccff"><td>Directories to Ignore:<br />(e.g. bin, dev, gallery.*)</td><td>
				<wxp module="wx" class="ListView" width="100%%" height="80" name="DirsToIgnore" style="wx.BORDER | wx.LC_EDIT_LABELS">
				</wxp></td><td>
			<wxp module="wx" class="Button" width="140" name="AddIgnoreDir" id="%s" label="Add Directory"></wxp><br />
			<wxp module="wx" class="Button" width="140" name="DeleteIgnoreDir" id="%s" label="Delete Directory"></wxp>
				</td></tr>
			</td></tr>
		<tr bgcolor="#dddddd"><td></td><td colspan="2">
			<wxp module="wx" class="Button" width="80" name="Save" id="%s" label="Save"></wxp>&nbsp;
			<wxp module="wx" class="Button" width="80" name="Cancel" id="%s" label="Cancel"></wxp>
			</td></tr>
		</table>
		</body></html>""" % (
			sitename,
			site['server'],
			site['username'],
			site['password'],
			site['localdir'],
			self.ID_LOCAL_DIR,
			site['remotedir'],
			self.ID_REMOTE_DIR,
			self.ID_ADD_IGNORE_DIR,
			self.ID_DELETE_IGNORE_DIR,
			self.ID_SAVE,
			self.ID_CANCEL,
		)
		self.HtmlWnd.SetPage(s)
		
		for l in site['dirstoignore']:
			self.HtmlWnd.DirsToIgnore.InsertStringItem(
				self.HtmlWnd.DirsToIgnore.GetItemCount(),
				l
			)
		
		self.Bind( wx.EVT_BUTTON, 
			self.OnLocalBrowse, 
			id = self.ID_LOCAL_DIR )
		
		self.Bind( wx.EVT_BUTTON, 
			self.OnFTPRemoteBrowse, 
			id = self.ID_REMOTE_DIR )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnAddIgnoreDir, 
			id = self.ID_ADD_IGNORE_DIR )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnDeleteIgnoreDir, 
			id = self.ID_DELETE_IGNORE_DIR )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnFTPSave, 
			id = self.ID_SAVE )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnCancel, 
			id = self.ID_CANCEL )
		
		self.HtmlWnd.SiteName.SetFocus()
		
	# -------------------------------------------------------------------
	def LoadLocalSite(self, sitename = ''):		
		if not sitename:
			site = {}
			site['localdir'] = ''
			site['localdir2'] = ''
			site['dirstoignore'] = []
		else:
			site = self.Sites[sitename]		
			
		s = """<html><body>
		<table width="100%%">
		<tr><td><img src="images/logo.png" /></td>
			<td align="right"><a href="Help">Help</a></td>
		</tr>
		</table>
		<table width="100%%" bgcolor="#ccccff" cellspacing="0">
		<tr>
			<td colspan=3"><h3>Create New Local Site</h3></td>
		</tr>
		<tr>
			<td>What would you like to call this site?</td>
			<td colspan="2"><wxp module="wx" class="TextCtrl" width="100%%" name="SiteName" value="%s"></wxp></td>
		</tr>
		<tr>
			<td colspan="3">Please choose two directories to synchronize:</td>
		</tr>
		<tr>
			<td>Directory 1:</td>
			<td><wxp module="wx" class="TextCtrl" width="100%%" name="LocalDir" value="%s"></wxp></td>
			<td><wxp module="wx" class="Button" width="80" name="BrowseLocalDir" id="%s" label="Browse"></wxp></td>
		</tr>
		<tr>
			<td>Directory 2:</td>
			<td><wxp module="wx" class="TextCtrl" width="100%%" name="LocalDir2" value="%s"></wxp></td>
			<td><wxp module="wx" class="Button" width="80" name="BrowseLocalDir2" id="%s" label="Browse"></wxp></td>
		<tr>
			<td>Directories to Ignore:<br />(e.g. bin, dev, gallery.*)</td>
			<td><wxp module="wx" class="ListView" width="300" height="80" name="DirsToIgnore" style="wx.BORDER | wx.LC_EDIT_LABELS"></wxp></td>
			<td><wxp module="wx" class="Button" width="140" name="AddIgnoreDir" id="%s" label="Add Directory"></wxp><br />
			<wxp module="wx" class="Button" width="140" name="DeleteIgnoreDir" id="%s" label="Delete Directory"></wxp></td>
		</tr>
		<tr bgcolor="#dddddd">
			<td></td>
			<td colspan="2"><wxp module="wx" class="Button" width="80" name="Save" id="%s" label="Save"></wxp>&nbsp;
			<wxp module="wx" class="Button" width="80" name="Cancel" id="%s" label="Cancel"></wxp></td>
		</tr>
		</table>
		</body></html>""" % (
			sitename,
			site['localdir'],
			self.ID_LOCAL_DIR,
			site['localdir2'],
			self.ID_LOCAL_DIR_2,
			self.ID_ADD_IGNORE_DIR,
			self.ID_DELETE_IGNORE_DIR,
			self.ID_SAVE,
			self.ID_CANCEL,
		)
		self.HtmlWnd.SetPage(s)
		
		for l in site['dirstoignore']:
			self.HtmlWnd.DirsToIgnore.InsertStringItem(
				self.HtmlWnd.DirsToIgnore.GetItemCount(),
				l
			)
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnLocalBrowse, 
			id = self.ID_LOCAL_DIR )
		
		self.Bind( wx.EVT_BUTTON, 
			self.OnLocalBrowse2, 
			id = self.ID_LOCAL_DIR_2 )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnAddIgnoreDir, 
			id = self.ID_ADD_IGNORE_DIR )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnDeleteIgnoreDir, 
			id = self.ID_DELETE_IGNORE_DIR )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnLocalSave, 
			id = self.ID_SAVE )
			
		self.Bind( wx.EVT_BUTTON, 
			self.OnCancel, 
			id = self.ID_CANCEL )
		
		self.HtmlWnd.SiteName.SetFocus()
		
	# -------------------------------------------------------------------
	def OnLink(self, e):
		h = e.GetLinkInfo().GetHref()	
		
		if h == 'NewFTPSite':
			self.LoadFTPSite()
		elif h == 'NewLocalSite':
			self.LoadLocalSite()
		else:
			parts = h.split('/')
			
			if len(parts) > 1:
				if not self.Sites.has_key( parts[1] ):
					print 'Unknown site: ', parts[1]
					return
					
				if parts[0] == 'EditSite':
						type = self.Sites[ parts[1] ]['type']
						if type == 'ftp':
							self.LoadFTPSite( parts[1] )
						elif type == 'local':
							self.LoadLocalSite( parts[1] )				
				elif parts[0] == 'ScanSite':
					p = SyncPanel( self.ParentWnd, 
										parts[1], 
										self.Sites[ parts[1] ])
					self.ParentWnd.AddPage(p, parts[1])
					self.ParentWnd.ChangeSelection( self.ParentWnd.GetPageCount() - 1 )
				else:
					print 'Unknown command: ', parts[0]
			
	# -------------------------------------------------------------------
	def OnLocalBrowse(self, e):
		dir = wx.DirSelector(parent = self)
		
		if dir:
			self.HtmlWnd.LocalDir.SetValue( dir )
		
	# -------------------------------------------------------------------
	def OnLocalBrowse2(self, e):
		dir = wx.DirSelector(parent = self)
		
		if dir:
			self.HtmlWnd.LocalDir2.SetValue( dir )
		
	# -------------------------------------------------------------------
	def OnFTPRemoteBrowse(self, e):
		server = self.HtmlWnd.FTPServer.GetValue()
		username = self.HtmlWnd.UserName.GetValue()
		password = self.HtmlWnd.Password.GetValue()		
		
		if not server or not username or not password:
			wx.MessageBox("""Please fill out the server, username, and password
before using the browse button.""",
				'Cannot Connect to Server',
				wx.ICON_HAND,
				self)
			return
		
		d = FTPDirDialog(self, 
										server, 
										username, 
										password, 
										self.GetDirsToIgnore())
		
		if d.ShowModal() == wx.ID_OK:
			self.HtmlWnd.RemoteDir.SetValue( d.Path )
		
	# -------------------------------------------------------------------
	def GetDirsToIgnore(self):
		len = self.HtmlWnd.DirsToIgnore.GetItemCount()
		ls = []
		
		for i in range(0, len):
			ls.append( self.HtmlWnd.DirsToIgnore.GetItemText(i) )
		
		return ls
		
	# -------------------------------------------------------------------
	def OnAddIgnoreDir(self, e):
		ls = self.GetDirsToIgnore()
		
		s = ''
		
		while True:
			s = wx.GetTextFromUser("""Please type in a directory name
that you want to avoid synchronizing.

Regular expressions such as bob.* to avoid all directories
starting with the letters bob are supported.""",
				'Add a Directory to Ignore',
				default_value = s,
				parent = self)
			
			if s in ls:
				wx.MessageBox("""The directory %s is already in the list
of directories to avoid.""",
					'Directory already exists')
			else:			
				self.HtmlWnd.DirsToIgnore.InsertStringItem(
				self.HtmlWnd.DirsToIgnore.GetItemCount(),
				s)
				break
		
	# -------------------------------------------------------------------
	def OnDeleteIgnoreDir(self, e):
		while True:
			i = self.HtmlWnd.DirsToIgnore.GetNextItem(-1, 
						state = wx.LIST_STATE_SELECTED)
			
			if i != -1:
				self.HtmlWnd.DirsToIgnore.DeleteItem( i )
			else:
				break
		
	# -------------------------------------------------------------------
	def OnFTPSave(self, e):
		site = {}
		sitename = self.HtmlWnd.SiteName.GetValue()
		site['type'] = 'ftp'
		site['server'] = self.HtmlWnd.FTPServer.GetValue()
		site['username'] = self.HtmlWnd.UserName.GetValue()
		site['password'] = self.HtmlWnd.Password.GetValue()		
		
		if not site['server'] or not site['username'] or not site['password']:
			wx.MessageBox("""Please fill out the server, username, and password.""",
				'Not Enough Information to Save',
				wx.ICON_HAND,
				self)
			return
		
		if not sitename:
			sitename = site['server']
		
		site['localdir'] = self.HtmlWnd.LocalDir.GetValue()
		site['remotedir'] = self.HtmlWnd.RemoteDir.GetValue()
		site['dirstoignore'] = self.GetDirsToIgnore()
		
		if not site['localdir'] or not site['remotedir']:
			wx.MessageBox("""Please select a local directory and
a remote directory to synchronize.""",
				'Not Enough Information to Save',
				wx.ICON_HAND,
				self)
			return
		
		self.Sites[sitename] = site
		self.SaveSites()
		self.LoadIndex()
		
	# -------------------------------------------------------------------
	def OnLocalSave(self, e):
		site = {}
		site['type'] = 'local'
		sitename = self.HtmlWnd.SiteName.GetValue()
		
		if not sitename:
			wx.MessageBox("""Please type a name for this site.""",
				'Not Enough Information to Save',
				wx.ICON_HAND,
				self)
			return
		
		site['localdir'] = self.HtmlWnd.LocalDir.GetValue()
		site['localdir2'] = self.HtmlWnd.LocalDir2.GetValue()
		site['dirstoignore'] = self.GetDirsToIgnore()
		
		if not site['localdir'] or not site['localdir2']:
			wx.MessageBox("""Please select two directories to synchronize.""",
				'Not Enough Information to Save',
				wx.ICON_HAND,
				self)
			return
		
		self.Sites[sitename] = site
		self.SaveSites()
		self.LoadIndex()
		
	# -------------------------------------------------------------------
	def OnCancel(self, e):
		self.LoadIndex()

# *********************************************************************
class MainFrame(wx.Frame):
	(
		ID_STATUS,
		ID_FILE,
		ID_GAUGE,
		ID_DOWNLOAD,
		ID_UPLOAD,
		ID_CANCEL,
	) = range(0x100, 0x106)	
	
	# -------------------------------------------------------------------
	def __init__(self):
		wx.Frame.__init__(self, None, wx.ID_ANY, '%s - %s' % (AppName, AppVersion) )
		
		self.Tabs = wx.Notebook(self, wx.ID_ANY)
		self.MainPanel = MainPanel(self.Tabs)
		
		self.Tabs.AddPage(self.MainPanel, 'Sync Sites')
		
		self.MainSizer = wx.BoxSizer( wx.VERTICAL )
		self.MainSizer.Add(self.Tabs, 1, wx.EXPAND)
		
		self.SetSizer( self.MainSizer) 
		
		if PossumConfig.Config.has_key('windowpos'):
			x, y, w, h = PossumConfig.Config['windowpos']
		else:
			x, y, w, h = (0, 0, 600, 400)
		
		wx.EVT_CLOSE(self, self.OnClose)
		
		self.SetDimensions(x, y, w, h)
		
		self.Show(True)
	
	# -------------------------------------------------------------------
	def OnClose(self, e):
		c = self.Tabs.GetPageCount()
		
		for i in range(1, c):
			t = self.Tabs.GetPage(i).Thread
			
			if t:
				t.Cancel()
		
		x, y = self.GetPositionTuple()
		w, h = self.GetSizeTuple()
		PossumConfig.Config['windowpos'] = (x, y, w, h)
		self.Destroy()
	
	# -------------------------------------------------------------------
	def OnAbout(self, e):
		wx.MessageBox("""A simple program for synchronizing your hard drive
with your website when your host does not provide SSH access.

Version %s

Written by Jackson Yee (jackson.yee@gmail.com)

Questions and comments are welcome.

Donations to help me develop this further
are even more welcome.""" % AppVersion,
			'About %s' % AppName)
			
	# -------------------------------------------------------------------
	def OnDownload(self, e):
		if self.InProgress:
			return
		
		self.SetProgress( True )
		self.StatusLabel.SetLabel( 'Connecting...' )
		
		t = SyncThread()
		t.Upload = False
		t.Wnd = self
		self.Thread = t
		t.start()
		
	# -------------------------------------------------------------------
	def OnUpload(self, e):
		if self.InProgress:
			return
			
		self.SetProgress( True )
		self.StatusLabel.SetLabel( 'Connecting...' )
		
		t = SyncThread()
		t.Upload = False
		t.Wnd = self
		self.Thread = t
		t.start()
		
	# -------------------------------------------------------------------
	def OnCancel(self, e):
		if not self.InProgress:
			return
		
		self.SetProgress( False )
		
	# -------------------------------------------------------------------
	def OnSyncUpdate(self, e):
		if e.status:
			if e.status == 'Finished':
				s = self.StatusLabel.GetLabel()
				
				if 'error' in s:
					self.StatusLabel.SetLabel( 'Finished with errors' )
				else:
					self.StatusLabel.SetLabel( 'Finished successfully' )
				
				self.SetProgress( False )
				self.FileLabel.SetLabel( 'File: ' )
				return
			else:
					self.StatusLabel.SetLabel( e.status )
			
		if e.file:
			self.FileLabel.SetLabel( 'File: ' + e.file )
		
		self.Gauge.Pulse()
		
			
# =====================================================================
def Run():
	PossumConfig.Load()
	app = wx.PySimpleApp()	
	wx.InitAllImageHandlers()
	
	frame = MainFrame()	
	app.MainLoop()
	PossumConfig.Save()
	
# =====================================================================
if __name__ == '__main__':
	Run()
